OS - Lab5实验报告
归档于2025年7月8日。
思考题
5.1
这跟Cache的更新策略有一些关系。
若Cache采用了写回策略,则:由kseg0写设备,向设备传递的数据会被存入Cache,而不会真正到达设备;仅在对应Cache块被清除掉时,向设备传输的数据才能到达设备。这会导致设备的延迟响应,甚至不响应。
Cache的更新策略,也与这个问题有关。如果Cache中缓存了设备的状态寄存器,那么:众所周知,设备的状态往往实时变化,若要保持Cache与设备状态寄存器的一致,我们必须时刻更新Cache,其性能开销极大,完全背离了Cache的设计本意;不维持一致性,则在Cache中读出的设备状态信息与实际的设备状态往往不符,会引起程序逻辑的问题。
这样做对不同设备的影响当然是不同的。对于IDE硬盘,可能是一次读/写请求迟迟不响应,导致程序长时间阻塞;对于显示设备(串口输出/显示器/…),可能是迟迟不显示该显示的内容。
5.2
答案在user/include/fs.h中:
FILE_STRUCT_SIZE:文件控制块大小,为256BLOCK_SIZE:磁盘块大小;与PAGE_SIZE相等,为4096
由此得知,一个磁盘块,最多可存储4096/256 = 16个文件控制块。
剩下两个问题,需要看f_(in)direct能指向多少个磁盘块;答案是10 + (BLOCKSIZE / 4 - 10) = 1024个。
对于文件,文件系统支持的单个文件大小,为4096 * 1024byte = 4MB。
对于目录,其下可以有1024 * 16 = 16k 个文件。
5.3
分析可知,我们的块缓存,采用了类似Cache中直接映射的思路,故缓冲区大小即等于最大硬盘容量。
查询serv.h得知,我们得知这一片空间大小为0x40000000byte;换算过来,就是1GB.
这些宏定义能不能起个好点的名字?DISKMAX一晃眼还以为是地址上限,结果仔细一看才发现是空间大小…况且之前都是用上限来界定空间,这里突然换成基地址+空间大小又是个什么意思
5.4
要我说,其实都重要…
- BLOCK_SIZE:硬盘块大小
- N_DIRECT:文件控制块中,直接指向硬盘块的指针个数
- FILE2BLK:一个硬盘块里,可存文件控制块的个数
- SECT_SIZE:一个扇区的大小
- SECT2BLK:一个硬盘块可以存多少个扇区;注意这二者是不一样的
- DISKMAP/MAX:指示内存中,硬盘缓冲区的起始位置与大小
5.5
在动手写程序之前,我们不妨先分析一番。
众所周知,fork()会将父进程的内存映射复制给子进程;此处指向Fd的内存映射,应该也是在被复制的内存映射范围内的。
1 | int main(){ |
测试下来,确实也是如此。
编写的时候需要注意:
- open()返回的是
fd的编号 - read()/write()中的参数,传入的都是
int fdnum,而非fd本身 file_read/write()是给驱动用的,用户态只需要调用open/write()。
5.6
先看fd.h里两个:
1 | 34 // file descriptor |
1 | 49 // file descriptor + file |
再看FIle结构体:
1 | 26 struct File { |
顺手提一下,Fd是怎么变成FileFd的:
- 在
serv.c的serve_open()中,先file_create()创建文件控制块, - 再
file_open(),将文件加载进文件控制块; - 最后,配置好FileFd,由fsipc,将
ffd传回给fsipc_open(path, mode, fd)中的fd,配合强制类型转化完成。
5.7
怎么OS也要跟UML语义纠缠
一共有这三种:
- Found Message(如
ENV_CREATE):无触发对象消息 - 同步消息:发送后,发送者停止自己的活动,如
ipc_send(fsreq),直到收到返回消息 - 返回消息:如
ipc_send(dst_va)
具体实现:
- Found Message:将信息存在一个指定空间内,发送者就什么都不做了;ENV_CREATE就是将进程放进调度队列里
- 同步消息:类似于握手机制,在一个
ipc_send()后,立即执行ipc_recv();在未收到信息时等待,收到后则程序恢复执行 - 返回信息:就是普通的
ipc_send(),只是为了配合同步消息的实现。
难点分析
文件系统

按照教程中示意图,我们可以得知:
- 用户进程通过调用库,请求文件服务,实现文件操作
file_read()/write()由用户库完成,而非直接调用文件服务- 文件服务单独为一个进程
- 用户进程由进程间通信,与服务进程交互
- 服务进程由中断进入内核,进行外部设备的文件交互
MMIO
malta.h中,诸如0x18000000一类的地址,指向的是物理地址。
在内核中,欲通过MMIO对这些外设进行交互,则需要访问一个映射到设备物理地址的虚拟地址;这个虚拟地址在kseg1内,物理地址加上kseg1的偏移,即可得到对应的虚拟地址。
对于MMIO的内存操作,指针建议都带上volatile修饰符,以避免编译器错误优化,带来预期外的结果:
kseg1看这名字就知道是内核态的地址空间;我们的IDE驱动程序在用户态,故我们需要一个系统调用,来对这个地址进行访问。
文件系统的空闲位图

- 位图的起点在Block[2]处,也就是Super Block的下一个Block
- 每一个字节设为0xff,即全设为1;一个块的字节大小即BLOCK_SIZE
- 第三步的
if,即“根据实际情况,将不存在部分设为0” - 在
fs/fs.c中,由read_bitmap(),将位图读入bitmap[]中。 *bitmap是一个uint32_t *,也就是一单位的bitmap[]有32个硬盘块的空闲位,这就是为什么下面free有关函数,要用bitmap[blockNo / 32]的形式访问位图。free_block()中,若blockno为0,则会将分区表和引导扇区free掉。参考删掉pagefile.sys会发生什么(笑)
create_file()
这个函数,实际做的是:
- 为文件分配一块空闲的空间。
- 首先,遍历目标目录;
- 先看是否有空闲File块,有则复用
- 完全遍历后,没有空闲File块,则为目标目录分配一个新的块;
- 新块的起始位置,就是目标
File块的位置。
map_block()
不要忘了,page_alloc是内核用的;用户态分配页,是通过syscall_mem_alloc()来实现的。
也别忘了,参数中envid = 0,指示当前进程。(syscall流程见本人Lab4文档…)
思考题用户态程序的编写
注意到,实验环境中已有的环境,已经生成了二进制机器码。
本人并没有找到效仿之,并在init.c中ENV_CREATE的较简便方法,遂选择直接修改init.c进行运行。
小细节
我记得最深的,就是设备物理地址是否合法的检验。
指导书里是这么写的(指导书给的范围没错):
1 | 同时还要检查物理地址的有效性,在实验中允许访问的地址范围为: |
然而直接按照上面的范围,把条件写成if ((pa >= 0x180003f8 && pa + len < 0x18000418)是错的,条件应该是if ((pa >= 0x180003f8 && pa + len <= 0x18000418)。
为什么?考虑基地址为0,空间大小为4;那么我们可访问的地址,应该是[0x0, 0x3],对吧?
但在上面,使用pa + len的情况下,0x0 + 4 = 0x4;此时我们应该是允许等于0x4的。
一个基本的数学小问题,需要细心,不能看到指导书就直接照搬。
思考题中提到的
比如说进程通信(fsipc),Fd到Filefd的转换过程等等,我认为有难度的地方,我都在思考题部分提到了;若您感兴趣,还请重新翻到上文阅读。
实验体会
本次实验虽然花了比我预期要多的时间(),但是难度比我预期要低。
我的感受就是,遇到不懂的,就边读边记,实在搞不懂就参考前人的经验。像Fd到FileFd的转换,我不边读源码,边在报告里记录其流程,我自己真不一定能搞定。
至于参考前人经验…就比如说上面提到的,设备物理地址合法性检验的问题,在我没查看MOS的开源代码前,我真没有意识到这个问题,花了不少时间找bug。当然,参考不等于抄,参考之后自己一定要理解。
原创声明
本文绝大部分内容为本人原创,但在实验过程中,确定部分细节是否实现正确时,本人难免参考了以下资料:
OS - Lab5实验报告